const frappe = require('frappe');
const mysql = require('mysql');
const Database = require('./database');
const debug = false;

module.exports = class mysqlDatabase extends Database {
  constructor({ db_name, username, password, host }) {
    super();
    this.db_name = db_name;
    this.username = username;
    this.password = password;
    this.host = host;
    this.init_typeMap();
  }

  connect(db_name) {
    if (db_name) {
      this.db_name = db_name;
    }
    return new Promise((resolve) => {
      this.conn = new mysql.createConnection({
        host: this.host,
        user: this.username,
        password: this.password,
        database: this.db_name,
      });
      () => {
        if (debug) {
          this.conn.on('trace', (trace) => console.log(trace));
        }
      };
      resolve();
    });
  }

  async tableExists(table) {
    const name = await this.sql(`SELECT table_name
            FROM information_schema.tables
            WHERE table_schema = '${this.db_name}'
            AND table_name = '${table}'`);
    return name && name.length ? true : false;
  }

  async runCreateTableQuery(doctype, columns, values) {
    const query = `CREATE TABLE IF NOT EXISTS ${doctype} (
            ${columns.join(', ')})`;

    return await this.run(query, values);
  }

  updateColumnDefinition(df, columns, indexes) {
    columns.push(
      `${df.fieldname} ${this.typeMap[df.fieldtype]} ${
        df.required && !df.default ? 'not null' : ''
      } ${df.default ? `default '${df.default}'` : ''}`
    );
  }

  async getTableColumns(doctype) {
    return (await this.sql(`SHOW COLUMNS FROM ${doctype}`)).map((d) => d.Field);
  }

  async runAddColumnQuery(doctype, fields) {
    await this.run(
      `ALTER TABLE ${doctype} ADD COLUMN ${this.get_column_definition(doctype)}`
    );
  }

  getOne(doctype, name, fields = '*') {
    fields = this.prepareFields(fields);

    return new Promise((resolve, reject) => {
      this.conn.get(
        `select ${fields} from ${doctype}
                where name = ?`,
        name,
        (err, row) => {
          resolve(row || {});
        }
      );
    });
  }

  async insertOne(doctype, doc) {
    let fields = this.get_keys(doctype);
    let placeholders = fields.map((d) => '?').join(', ');

    if (!doc.name) {
      doc.name = frappe.getRandomString();
    }

    return await this.run(
      `insert into ${doctype}
            (${fields.map((field) => field.fieldname).join(', ')})
            values (${placeholders})`,
      this.getFormattedValues(fields, doc)
    );
  }

  async updateOne(doctype, doc) {
    let fields = this.getKeys(doctype);
    let assigns = fields.map((field) => `${field.fieldname} = ?`);
    let values = this.getFormattedValues(fields, doc);

    // additional name for where clause
    values.push(doc.name);

    return await this.run(
      `update ${doctype}
                set ${assigns.join(', ')} where name=?`,
      values
    );
  }

  async runDeleteOtherChildren(field, added) {
    await this.run(
      `delete from ${field.childtype}
            where
                parent = ? and
                name not in (${added
                  .slice(1)
                  .map((d) => '?')
                  .join(', ')})`,
      added
    );
  }

  async deleteOne(doctype, name) {
    return await this.run(`delete from ${doctype} where name=?`, name);
  }

  async deleteChildren(parenttype, parent) {
    await this.run(`delete from ${parent} where parent=?`, parent);
  }

  getAll({
    doctype,
    fields,
    filters,
    start,
    limit,
    order_by = 'modified',
    order = 'desc',
  } = {}) {
    if (!fields) {
      fields = frappe.getMeta(doctype).getKeywordFields();
    }
    return new Promise((resolve, reject) => {
      let conditions = this.getFilterConditions(filters);

      this.conn.all(
        `select ${fields.join(', ')}
                from ${doctype}
                ${conditions.conditions ? 'where' : ''} ${conditions.conditions}
                ${order_by ? 'order by ' + order_by : ''} ${
          order_by ? order || 'asc' : ''
        }
                ${limit ? 'limit ' + limit : ''} ${
          start ? 'offset ' + start : ''
        }`,
        conditions.values,
        (err, rows) => {
          if (err) {
            reject(err);
          } else {
            resolve(rows);
          }
        }
      );
    });
  }

  run(query, params) {
    // TODO promisify
    return new Promise((resolve, reject) => {
      this.conn.query(query, params, (err) => {
        if (err) {
          if (debug) {
            console.error(err);
          }
          reject(err);
        } else {
          resolve();
        }
      });
    });
  }

  sql(query, params) {
    return new Promise((resolve) => {
      this.conn.query(query, params, (err, rows) => {
        resolve(rows);
      });
    });
  }

  async commit() {
    try {
      await this.run('commit');
    } catch (e) {
      if (e.errno !== 1) {
        throw e;
      }
    }
  }

  init_typeMap() {
    this.typeMap = {
      AutoComplete: 'VARCHAR(140)',
      Currency: 'real',
      Int: 'INT',
      Float: 'decimal(18,6)',
      Percent: 'real',
      Check: 'INT(1)',
      'Small Text': 'text',
      'Long Text': 'text',
      Code: 'text',
      'Text Editor': 'text',
      Date: 'DATE',
      Datetime: 'DATETIME',
      Time: 'TIME',
      Text: 'text',
      Data: 'VARCHAR(140)',
      Link: ' varchar(140)',
      DynamicLink: 'text',
      Password: 'varchar(140)',
      Select: 'VARCHAR(140)',
      'Read Only': 'varchar(140)',
      File: 'text',
      Attach: 'text',
      'Attach Image': 'text',
      Signature: 'text',
      Color: 'text',
      Barcode: 'text',
      Geolocation: 'text',
    };
  }
};
